47. 创建N维数组

本章核心:NumPy数组是数据分析的基石

  • NumPy(Numerical Python)是Python科学计算的核心库
  • 其核心数据结构是 ndarray(N-dimensional array,N维数组)
  • 相比Python列表,NumPy数组在内存效率计算性能上具有巨大优势
  • 本章学习:创建一维/二维数组、特殊数组、数组属性与运算

张量(Tensor)与多维数组的关系

从数学角度,NumPy数组实现了张量的概念:

维度 数学名称 示例
0维 标量(Scalar) \(5\)
1维 向量(Vector) \([1, 2, 3]\)
2维 矩阵(Matrix) \([[1,2],[3,4]]\)
3维+ 高维张量 时间序列数据立方体

对于 \(d\) 维张量 \(\mathbf{A}\),元素索引为 \(\mathbf{A}_{i_1, i_2, \ldots, i_d}\)

NumPy数组 vs Python列表

特性 NumPy数组 Python列表
内存布局 连续内存块 分散的对象引用
数据类型 同构(类型相同) 异构(可混合)
内存效率 紧凑(int32占4字节) 每个对象约28字节
计算性能 向量化(C层面) 解释执行(Python层面)

NumPy快的秘密:向量化运算 + C语言底层实现

为什么NumPy如此重要?

  • 向量化运算:避免Python循环,利用CPU的SIMD指令
  • 广播机制:自动对齐不同形状的数组
  • C语言底层:核心代码用C实现,速度接近编译语言
  • 丰富的函数库:线性代数、傅里叶变换、随机数生成等

⭐ 平台任务解答代码

Listing 1
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
import numpy as np #导入NumPy模块并且用英文缩写np

price_amazon= np.array([170.23,170.10,177.59,177.06,178.22])    #直接创建一维数组

type (price_amazon)  # 查看price_amazon的数据类型

price_app1e=[221.27,221.72,224.72,226.05,225.89]  # 定义列表price_app1e

price_apple=np.array(price_app1e)                 #将列表转换为一维数组 

type(price_apple)  # 查看price_apple的数据类型

#任务二
import numpy as np

data1 = [170.23,170.10,177.59,177.06,178.22]  # 定义列表data1
data2 = [221.27,221.72,224.72,226.05,225.89]  # 定义列表data2
data3 = [413.26,416.11,421.03,418.47,421.53]  # 定义列表data3
data4 = [164.16,160.37,161.30,162.96,166.67]  # 定义列表data4
data5 = [648.02,661.68,663.22,674.07,688.53]  # 定义列表data5

price_array=np.array([data1,data2,data3,data4,data5]) #将以上列表数据创建成数组

print(price_array.shape)  #查看数组的形状
print(price_array.ndim)  #查看数组的维度
print(price_array.size)  #查看数组的元素个数
print(price_array.dtype)  #查看数组中的元素类型
  

#任务三
import numpy as np

price_app1e=[221.27,221.72,224.72,226.05,225.89]  # 定义列表price_app1e

n=price_array.size  #变量n赋值为数组的元素个数

data1 = [170.23,170.10,177.59,177.06,178.22]  # 定义列表data1
data2 = [221.27,221.72,224.72,226.05,225.89]  # 定义列表data2
data3 = [413.26,416.11,421.03,418.47,421.53]  # 定义列表data3
data4 = [164.16,160.37,161.30,162.96,166.67]  # 定义列表data4
data5 = [648.02,661.68,663.22,674.07,688.53]  # 定义列表data5

price_array=np.array([data1,data2,data3,data4,data5]) #将以上列表数据创建成数组

n=price_array.size  #变量n赋值为数组的元素个数

x=np.arange(n+1)  #生成整数序列
m=40  # 设置数组长度参数为40
y=np.linspace(price_apple[0],price_apple[-1],m)#生成起始值是苹果公司股票5月13日收盘价、终止值是5月17日收盘价且元素数量40的等差序列
print(y)  # 输出变量y的值

#任务四
import numpy as np

price_app1e=[221.27,221.72,224.72,226.05,225.89]  # 定义列表price_app1e

price_amazon=np.array([1822.68,1840,12,1871.15,1907.57,1869.00])  # 创建NumPy数组price_amazon

zero_arrayl=np.zeros_like(price_amazon)#快速生成元素为零的一维数组
zero_array2=np.zeros_like(price_array)#快速生成元素为零的二维数组
one_arrayl=np.ones_like(price_amazon)#快速生成元素等于1的一维数组
one_array2=np.ones_like(price_array) #快速生成元素等于1的二维数组
print(zero_arrayl)  # 输出变量zero_arrayl的值
print(zero_array2)  # 输出变量zero_array2的值
print(one_arrayl)  # 输出变量one_arrayl的值
print(one_array2)  # 输出变量one_array2的值
(5, 5)
2
25
float64
[221.27       221.38846154 221.50692308 221.62538462 221.74384615
 221.86230769 221.98076923 222.09923077 222.21769231 222.33615385
 222.45461538 222.57307692 222.69153846 222.81       222.92846154
 223.04692308 223.16538462 223.28384615 223.40230769 223.52076923
 223.63923077 223.75769231 223.87615385 223.99461538 224.11307692
 224.23153846 224.35       224.46846154 224.58692308 224.70538462
 224.82384615 224.94230769 225.06076923 225.17923077 225.29769231
 225.41615385 225.53461538 225.65307692 225.77153846 225.89      ]
[0. 0. 0. 0. 0. 0.]
[[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]
[1. 1. 1. 1. 1. 1.]
[[1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]]

创建一维数组:三种基本方法

Listing 2
import numpy as np

# 方法1:从Python列表创建
arr1 = np.array([1, 2, 3, 4, 5])

# 方法2:arange() — 指定步长生成序列
arr2 = np.arange(10)  # [0, 1, 2, ..., 9]

# 方法3:linspace() — 指定元素个数生成等间距数列
arr3 = np.linspace(0, 1, 5)  # [0., 0.25, 0.50, 0.75, 1.00]

print('arange:', arr2)
print('linspace:', arr3)
arange: [0 1 2 3 4 5 6 7 8 9]
linspace: [0.   0.25 0.5  0.75 1.  ]

arange() vs linspace() 的核心区别

函数 参数含义 是否包含终止值
np.arange(start, stop, step) 指定步长 不包含
np.linspace(start, stop, num) 指定元素个数 包含
# arange:不包含终止值1.0
np.arange(0, 1, 0.2)    # [0., 0.2, 0.4, 0.6, 0.8]

# linspace:包含终止值1.0
np.linspace(0, 1, 6)    # [0., 0.2, 0.4, 0.6, 0.8, 1.0]

数据类型的自动推断与显式指定

# NumPy自动推断数据类型
int_arr = np.array([1, 2, 3])
print(int_arr.dtype)     # int64

float_arr = np.array([1.0, 2.0, 3.0])
print(float_arr.dtype)   # float64

# 显式指定数据类型
arr_f32 = np.array([1, 2, 3], dtype=np.float32)
print(arr_f32.dtype)     # float32
  • float32float64 节省一半内存
  • 金融计算推荐 float64:精度更高,避免累积误差

创建二维数组:两种常用方法

Listing 3
import numpy as np

# 方法1:从列表的列表创建
arr2d = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
])

# 方法2:先创建一维数组,再reshape重塑
arr_3x4 = np.arange(12).reshape(3, 4)

print('2D数组(3x3):')
print(arr2d)

print(f'\n3x4数组:')
print(arr_3x4)
2D数组(3x3):
[[1 2 3]
 [4 5 6]
 [7 8 9]]

3x4数组:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

reshape() 的核心规则

  • 元素总数必须匹配3×4=12 的数组只能重塑为元素总数为12的形状
  • 使用 -1 自动推断:让NumPy自动计算某一维的大小
arr = np.arange(12)   # 12个元素
arr.reshape(3, 4)     # 3×4=12 ✓
arr.reshape(4, 3)     # 4×3=12 ✓
arr.reshape(3, -1)    # 自动推断为(3, 4)
arr.reshape(-1, 4)    # 自动推断为(3, 4)
# arr.reshape(5, 3)   # 5×3=15 ✗ 错误!

数组形状与元素访问

arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr.shape)   # (2, 3) — 2行3列

# 访问特定位置的元素
print(arr[0, 1])   # 2 — 第0行,第1列
print(arr[1, 2])   # 6 — 第1行,第2列
  • shape 返回元组,表示各维度的大小
  • 索引从 0 开始

创建特殊数组

Listing 4
import numpy as np

# 全0数组
zeros = np.zeros((2, 3))
print('全0数组(2x3):')
print(zeros)

# 全1数组
ones = np.ones((3, 4))
print('\n全1数组(3x4):')
print(ones)

# 单位矩阵(对角线为1,其余为0)
identity = np.eye(3)
print('\n单位矩阵(3x3):')
print(identity)
全0数组(2x3):
[[0. 0. 0.]
 [0. 0. 0.]]

全1数组(3x4):
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]

单位矩阵(3x3):
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

对角矩阵与随机数组

Listing 5
import numpy as np

# 对角矩阵
diag = np.diag([1, 2, 3])
print('对角矩阵:')
print(diag)

# 标准正态分布随机数组(均值0,标准差1)
random_arr = np.random.randn(2, 3)
print('\n随机数组(2x3):')
print(random_arr)
对角矩阵:
[[1 0 0]
 [0 2 0]
 [0 0 3]]

随机数组(2x3):
[[-2.03257029  1.0540231  -0.45124494]
 [-1.71440258 -0.16071291 -0.9821243 ]]

特殊数组的数学意义

数组 函数 数学性质
零矩阵 np.zeros() \(O + A = A\)
全1矩阵 np.ones() 常用于广播运算
单位矩阵 np.eye() \(I \cdot A = A \cdot I = A\)
对角矩阵 np.diag() 协方差矩阵的简化形式

zeros_like / ones_like:快速生成同形数组

price_amazon = np.array([170.23, 170.10, 177.59])

# 生成与原数组形状和类型相同的全0/全1数组
zero_arr = np.zeros_like(price_amazon)
one_arr = np.ones_like(price_amazon)

print(zero_arr)  # [0. 0. 0.]
print(one_arr)   # [1. 1. 1.]
  • 保持与原数组相同的形状和数据类型
  • 适合初始化与已有数组结构一致的新数组

数组的四大核心属性

Listing 6
import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6]])

print('形状 shape:', arr.shape)      # (2, 3)
print('维度 ndim:', arr.ndim)        # 2
print('元素总数 size:', arr.size)    # 6
print('数据类型 dtype:', arr.dtype)  # int64

# reshape改变形状
arr_reshaped = arr.reshape(3, 2)
print('\n重塑后(3x2):')
print(arr_reshaped)
形状 shape: (2, 3)
维度 ndim: 2
元素总数 size: 6
数据类型 dtype: int32

重塑后(3x2):
[[1 2]
 [3 4]
 [5 6]]

维度(ndim)的直观理解

ndim 结构 Python创建方式
0 标量 np.array(5)
1 向量 np.array([1,2,3])
2 矩阵 np.array([[1,2],[3,4]])
3 张量 np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
  • 维度决定了数据的组织方式
  • 不同维度的操作对应不同的 axis 参数

展平数组:ravel() vs flatten()

arr = np.array([[1, 2, 3], [4, 5, 6]])

# ravel() 返回视图(view)— 修改会影响原数组
flat_view = arr.ravel()

# flatten() 返回副本(copy)— 修改不影响原数组
flat_copy = arr.flatten()
  • ravel():性能更好,但注意共享内存
  • flatten():更安全,修改互不影响

数组的元素级运算

Listing 7
import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 元素级运算(对应位置分别计算)
print('加法:', a + b)     # [5 7 9]
print('减法:', a - b)     # [-3 -3 -3]
print('乘法:', a * b)     # [4 10 18](注意:不是矩阵乘法!)
print('除法:', a / b)     # [0.25 0.4 0.5]
print('幂运算:', a ** 2)  # [1 4 9]
加法: [5 7 9]
减法: [-3 -3 -3]
乘法: [ 4 10 18]
除法: [0.25 0.4  0.5 ]
幂运算: [1 4 9]

数组的统计运算

Listing 8
import numpy as np

a = np.array([1, 2, 3])

print(f'均值: {a.mean():.2f}')    # 2.00
print(f'标准差: {a.std():.2f}')   # 0.82
print(f'方差: {a.var():.2f}')     # 0.67
print(f'最大值: {a.max()}')       # 3
print(f'最小值: {a.min()}')       # 1
print(f'求和: {a.sum()}')         # 6
均值: 2.00
标准差: 0.82
方差: 0.67
最大值: 3
最小值: 1
求和: 6

广播机制(Broadcasting)

不同形状的数组也可以运算——NumPy自动扩展较小的数组:

# 标量与数组
5 + np.array([1, 2, 3])  # [6, 7, 8]

# (3,1) 与 (3,) 的广播
arr1 = np.array([[1], [2], [3]])  # shape: (3,1)
arr2 = np.array([10, 20, 30])    # shape: (3,)
result = arr1 + arr2
# [[11, 21, 31],
#  [12, 22, 32],
#  [13, 23, 33]]

元素乘法 vs 矩阵乘法

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

# 元素乘法(*):对应位置相乘
a * b    # [[ 5, 12], [21, 32]]

# 矩阵乘法(@):线性代数的矩阵乘法
a @ b    # [[19, 22], [43, 50]]
  • * → 元素级(element-wise)
  • @np.dot() → 矩阵乘法(线性代数)

axis参数:沿哪个方向计算?

arr = np.array([[1, 2, 3],
                [4, 5, 6],
                [7, 8, 9]])

arr.mean()         # 5.0 — 全局均值
arr.mean(axis=0)   # [4., 5., 6.] — 沿列方向(↓)
arr.mean(axis=1)   # [2., 5., 8.] — 沿行方向(→)
  • axis=0:沿方向压缩(对每操作)
  • axis=1:沿方向压缩(对每操作)

布尔索引:按条件筛选元素

arr = np.array([1, 2, 3, 4, 5])

# 比较运算返回布尔数组
mask = arr > 3
print(mask)          # [False, False, False, True, True]

# 使用布尔索引筛选
filtered = arr[mask]
print(filtered)      # [4, 5]
  • 先用条件生成布尔掩码
  • 再用掩码筛选满足条件的元素

金融应用:计算收益率

Listing 9
import numpy as np

# 5个交易日的收盘价
prices = np.array([100, 105, 103, 108, 106])

# 简单收益率:R_t = (P_t - P_{t-1}) / P_{t-1}
simple_returns = (prices[1:] - prices[:-1]) / prices[:-1]

# 对数收益率:r_t = ln(P_t / P_{t-1})
log_returns = np.log(prices[1:] / prices[:-1])

print('简单收益率:')
print(simple_returns)
print('\n对数收益率:')
print(log_returns)
简单收益率:
[ 0.05       -0.01904762  0.04854369 -0.01851852]

对数收益率:
[ 0.04879016 -0.01923136  0.04740224 -0.01869213]

简单收益率 vs 对数收益率

类型 公式 特点
简单收益率 \(R_t = \frac{P_t - P_{t-1}}{P_{t-1}}\) 直观,但不可跨期相加
对数收益率 \(r_t = \ln\frac{P_t}{P_{t-1}}\) 时间可加性\(r_{total} = r_1 + r_2\)
  • 对数收益率在统计分析中更常用
  • 当收益率较小时,两者近似相等

累计收益率的计算

Listing 10
import numpy as np

prices = np.array([100, 105, 103, 108, 106])
simple_returns = (prices[1:] - prices[:-1]) / prices[:-1]

# 累计收益率 = (1+R_1) × (1+R_2) × ... × (1+R_n) - 1
cum_returns = (1 + simple_returns).cumprod() - 1

print('累计收益率:')
print(cum_returns)
print(f'\n总收益率: {cum_returns[-1]:.4f}')
累计收益率:
[0.05 0.03 0.08 0.06]

总收益率: 0.0600
  • cumprod() 计算累积乘积
  • 初始100元 → 经过5天 → 最终价值可直接由累计收益率推算

最佳实践总结

  • 数据类型:金融计算优先 float64,大数据集可用 float32
  • 向量化运算:避免Python循环,使用NumPy内置函数
  • 广播机制:利用自动形状对齐简化代码
  • 内存优化:用 reshape(-1) 自动推断,用 ravel() 避免复制
  • 数值稳定:用 np.log1p(x) 代替 np.log(1+x) 提高小数值精度